package com.qfmy.web.app.service.impl.listenBook;

import com.github.xiaoymin.knife4j.core.util.CollectionUtils;
import com.qfmy.common.exception.BaseException;
import com.qfmy.common.login.LoginUser;
import com.qfmy.common.login.LoginUserHolder;
import com.qfmy.common.result.ResultCodeEnum;
import com.qfmy.model.entity.listenBook.AudioResource;
import com.qfmy.model.enums.AudioType;
import com.qfmy.model.vo.listenBook.AudioResourceVo;
import com.qfmy.web.app.lucene.LuceneUtils;
import com.qfmy.web.app.mapper.listenBook.AudioResourceMapper;
import com.qfmy.web.app.service.conmon.SearchLogService;
import com.qfmy.web.app.service.listenBook.AudioResourceService;
import lombok.extern.slf4j.Slf4j;
import org.jetbrains.annotations.Contract;
import org.jetbrains.annotations.Nullable;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import org.springframework.stereotype.Service;

import java.util.Collections;
import java.util.List;
import java.util.UUID;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.stream.Collectors;

/**
 * @author 清风明月
 * @Package com.qfmy.web.app.service.impl.listenBook
 * @date 2025/11/1
 * @description 听书
 */
@Slf4j
@Service
public class AudioResourceServiceImpl implements AudioResourceService {

    /**
     * 注入mapper
     */
    @Autowired
    private AudioResourceMapper audioResourceMapper;

    /**
     * 注入redisTemplate
     */
    @Autowired
    private RedisTemplate<String, Object> redisTemplate;

    /**
     * 注入搜索日志
     */
    @Autowired
    private SearchLogService searchLogService;

    /**
     * 自定义一个线程池
     */
    private final ScheduledExecutorService renewalExecutor = Executors.newSingleThreadScheduledExecutor(
            r -> new Thread(r, "rank-lock-renewal-thread")
    );

    /**
     * 获取推荐排行
     * @param pageNum
     * @param pageSize
     * @return
     */
    @Override
    public List<AudioResourceVo> getRecommendRank(Integer pageNum, Integer pageSize) {
        // 1. 校验分页参数（避免非法值）
        pageNum = pageNum == null || pageNum < 1 ? 1 : pageNum;
        pageSize = pageSize == null || pageSize < 1 || pageSize > 50 ? 10 : pageSize;

        // 2. 构建带分页的缓存Key（不同分页对应不同缓存，避免覆盖）
        String cacheKey = String.format("listenBook:audioResource:recommendRank:pageNum:%d:pageSize:%d", pageNum, pageSize);

        // 3. 先查缓存：存在则直接返回（安全类型转换）
        Object cacheData = redisTemplate.opsForValue().get(cacheKey);
        if (cacheData instanceof List<?>) {
            List<?> list = (List<?>) cacheData;
            if (!CollectionUtils.isEmpty(list) && list.get(0) instanceof AudioResourceVo) {
                return (List<AudioResourceVo>) cacheData;
            }
            // 缓存数据类型异常：删除脏数据
            redisTemplate.delete(cacheKey);
            log.warn("推荐榜单缓存数据类型异常，已删除脏数据：{}", cacheKey);
        }

        // 4. 缓存不存在：加Redis分布式锁（避免缓存击穿）
        String lockKey = "listenBook:audioResource:recommendRank:lock";
        String lockValue = UUID.randomUUID().toString();
        AtomicBoolean isLockOwner = new AtomicBoolean(false); // 标记是否为锁持有者
        Future<?> renewalFuture = null;

        try {
            // 加锁：3秒过期（避免死锁），原子操作
            Boolean lockAcquired = redisTemplate.opsForValue().setIfAbsent(lockKey, lockValue, 3, TimeUnit.SECONDS);
            if (Boolean.TRUE.equals(lockAcquired)) {
                isLockOwner.set(true);
                log.info("获取推荐榜单锁成功，lockKey：{}，lockValue：{}", lockKey, lockValue);

                // 启动锁续期（看门狗）：每1秒续期3秒，避免查询耗时过长导致锁过期
                renewalFuture = startLockRenewal(lockKey, lockValue);

                // 二次查缓存：防止锁等待期间其他线程已更新缓存
                Object doubleCheckCache = redisTemplate.opsForValue().get(cacheKey);
                if (doubleCheckCache instanceof List<?> && !CollectionUtils.isEmpty((List<?>) doubleCheckCache)) {
                    return (List<AudioResourceVo>) doubleCheckCache;
                }

                // 查数据库：分页查询推荐榜单（使用PageHelper实现分页）
                int startIndex = (pageNum - 1) * pageSize;
                List<AudioResource> dbData = audioResourceMapper.getRecommendRank(startIndex, pageSize); // 需修改Mapper支持分页
                List<AudioResourceVo> resultVo = convertToVo(dbData); // 确保该方法处理null（返回空列表）

                // 写缓存：空数据短缓存（避免缓存穿透），非空数据正常缓存
                long ttl = CollectionUtils.isEmpty(resultVo)
                        ? 1
                        : 8;
                redisTemplate.opsForValue().set(cacheKey, resultVo, ttl, TimeUnit.SECONDS);
                log.info("推荐榜单查询完成，缓存已更新，cacheKey：{}，数据量：{}", cacheKey, resultVo.size());

                return resultVo;
            }

            // 5. 未获取到锁：重试获取缓存（避免直接返回空）
            log.info("未获取到推荐榜单锁，进入重试逻辑，lockKey：{}", lockKey);
            return retryGet(cacheKey);

        } catch (Exception e) {
            log.error("获取推荐榜单异常，pageNum：{}，pageSize：{}", pageNum, pageSize, e);
            throw new BaseException(404,"获取推荐榜单失败，请稍后重试"); // 抛自定义业务异常，上层统一处理
        } finally {
            // 6. 释放锁：仅删除自己持有的锁（原子操作，避免误删）
            if (isLockOwner.get()) {
                releaseLockSafely(lockKey, lockValue);
                log.info("推荐榜单锁已释放，lockKey：{}，lockValue：{}", lockKey, lockValue);
            }
            // 7. 停止锁续期任务
            if (renewalFuture != null && !renewalFuture.isDone()) {
                renewalFuture.cancel(true);
                log.debug("推荐榜单锁续期任务已中断，lockKey：{}", lockKey);
            }
        }
    }

    /**
     * 获取热播排行
     * @param pageNum
     * @param pageSize
     * @return
     */
    @Override
    public List<AudioResourceVo> getHotPlay(Integer pageNum, Integer pageSize) {
        // 1. 校验分页参数（避免非法值）
        pageNum = pageNum == null || pageNum < 1 ? 1 : pageNum;
        pageSize = pageSize == null || pageSize < 1 || pageSize > 50 ? 10 : pageSize;
        // 2. 构建带分页的缓存Key（不同分页对应不同缓存，避免覆盖）
        String cacheKey = String.format("listenBook:audioResource:hotPlay:pageNum:%d:pageSize:%d", pageNum, pageSize);
        // 3. 先查缓存：存在则直接返回（安全类型转换）
        Object cacheData = redisTemplate.opsForValue().get(cacheKey);
        if (cacheData instanceof List<?>) {
            List<?> list = (List<?>) cacheData;
            if (!CollectionUtils.isEmpty(list) && list.get(0) instanceof AudioResourceVo) {
                return (List<AudioResourceVo>) cacheData;
            }
            // 缓存数据类型异常：删除脏数据
            redisTemplate.delete(cacheKey);
            log.warn("热播数据缓存数据类型异常，已删除脏数据：{}", cacheKey);
        }
        // 4. 缓存不存在：加Redis分布式锁（避免缓存击穿）
        String lockKey = "listenBook:audioResource:hotPlay:lock";
        String lockValue = UUID.randomUUID().toString();
        AtomicBoolean isLockOwner = new AtomicBoolean(false);
        Future<?> renewalFuture = null;
        try {
            Boolean lockAcquired = redisTemplate.opsForValue().setIfAbsent(lockKey, lockValue, 3, TimeUnit.SECONDS);
            if (Boolean.TRUE.equals(lockAcquired)) {
                isLockOwner.set(true);
                // 获取锁成功，启动锁续期（看门狗）
                renewalFuture = startLockRenewal(lockKey, lockValue);
                log.info("获取热播数据锁成功，lockKey：{}，lockValue：{}", lockKey, lockValue);
                // 二次查缓存：防止锁等待期间其他线程已更新缓存
                Object doubleCheckCache = redisTemplate.opsForValue().get(cacheKey);
                if (doubleCheckCache instanceof List<?> && !CollectionUtils.isEmpty((List<?>) doubleCheckCache)) {
                    return (List<AudioResourceVo>) doubleCheckCache;
                }
                //计算分页
                int startIndex = (pageNum - 1) * pageSize;
                // 查询数据库
                List<AudioResource> dbData = audioResourceMapper.getHotPlay(startIndex, pageSize);
                //转换为VO
                List<AudioResourceVo> resultVo = convertToVo(dbData);
                //写缓存
                long ttl = CollectionUtils.isEmpty(resultVo) ? 1:  8;
                redisTemplate.opsForValue().set(cacheKey, resultVo, ttl, TimeUnit.SECONDS);
                log.info("热播数据查询完成，缓存已更新，cacheKey：{}，数据量：{}", cacheKey, resultVo.size());
                return resultVo;
            }else{
                log.info("未获取到热播数据锁，进入重试逻辑，lockKey：{}", lockKey);
                return retryGet(cacheKey);
            }
        }catch (Exception e)
        {
            log.error("获取热播数据异常", e);
            return Collections.emptyList();
        }finally {
            if (isLockOwner.get()) {
                releaseLockSafely(lockKey, lockValue);
                log.info("热播数据锁已释放，lockKey：{}，lockValue：{}", lockKey, lockValue);
            }
            if (renewalFuture != null && !renewalFuture.isDone()) {
                renewalFuture.cancel(true);
                log.debug("热播数据锁续期任务已中断，lockKey：{}", lockKey);
            }
        }
    }

    /**
     * 获取完结排行
     * @param pageNum
     * @param pageSize
     * @return
     */
    @Override
    public List<AudioResourceVo> getFinish(Integer pageNum, Integer pageSize) {
        // 1. 校验分页参数（避免非法值）
        pageNum = pageNum == null || pageNum < 1 ? 1 : pageNum;
        pageSize = pageSize == null || pageSize < 1 || pageSize > 50 ? 10 : pageSize;
        // 2. 构建带分页的缓存Key（不同分页对应不同缓存，避免覆盖）
        String cacheKey = String.format("listenBook:audioResource:finish:pageNum:%d:pageSize:%d", pageNum, pageSize);
        // 3. 先查缓存：存在则直接返回（安全类型转换）
        Object cacheData = redisTemplate.opsForValue().get(cacheKey);
        if (cacheData instanceof List<?>) {
            List<?> list = (List<?>) cacheData;
            if (!CollectionUtils.isEmpty(list) && list.get(0) instanceof AudioResourceVo) {
                return (List<AudioResourceVo>) cacheData;
            }
            // 缓存数据类型异常：删除脏数据
            redisTemplate.delete(cacheKey);
            log.warn("推荐榜单缓存数据类型异常，已删除脏数据：{}", cacheKey);
        }
        // 4. 缓存不存在：加Redis分布式锁（避免缓存击穿）
        String lockKey = "listenBook:audioResource:finish:lock";
        String lockValue = UUID.randomUUID().toString();
        AtomicBoolean isLockOwner = new AtomicBoolean(false);
        Future<?> renewalFuture = null;
        try {
            Boolean lockAcquired = redisTemplate.opsForValue().setIfAbsent(lockKey, lockValue, 3, TimeUnit.SECONDS);
            if (Boolean.TRUE.equals(lockAcquired)) {
                isLockOwner.set(true);
                // 获取锁成功，启动锁续期（看门狗）
                renewalFuture = startLockRenewal(lockKey, lockValue);
                log.info("获取推荐榜单锁成功，lockKey：{}，lockValue：{}", lockKey, lockValue);
                // 二次查缓存：防止锁等待期间其他线程已更新缓存
                Object doubleCheckCache = redisTemplate.opsForValue().get(cacheKey);
                if (doubleCheckCache instanceof List<?> && !CollectionUtils.isEmpty((List<?>) doubleCheckCache)) {
                    return (List<AudioResourceVo>) doubleCheckCache;
                }

                //计算分页
                int startIndex = (pageNum - 1) * pageSize;
                List<AudioResource> dbData = audioResourceMapper.getFinish(startIndex, pageSize);
                List<AudioResourceVo> resultVo = convertToVo(dbData);
                //写缓存
                long ttl = CollectionUtils.isEmpty(resultVo) ? 1 : 8;
                redisTemplate.opsForValue().set(cacheKey, resultVo, ttl, TimeUnit.SECONDS);
                log.info("推荐榜单查询完成，缓存已更新，cacheKey：{}，数据量：{}", cacheKey, resultVo.size());
                return resultVo;
            }else{
                log.info("未获取到推荐榜单锁，进入重试逻辑，lockKey：{}", lockKey);
                return retryGet(cacheKey);
            }
        }catch (Exception e)
        {
            log.error("获取推荐榜单异常", e);
            return Collections.emptyList();
        }finally {
            // 停止续期线程
            if (isLockOwner.get()) {
                releaseLockSafely(lockKey, lockValue);
                log.info("热播数据锁已释放，lockKey：{}，lockValue：{}", lockKey, lockValue);
            }
            // 取消续期任务
            if (renewalFuture != null && !renewalFuture.isDone()) {
                renewalFuture.cancel(true);
                log.debug("热播数据锁续期任务已中断，lockKey：{}", lockKey);
            }
        }
    }

    /**
     *  最新
     * @return
     */
    @Override
    public List<AudioResourceVo> getNewestAudio() {
        //创建缓存key
        String cacheKey = "listenBook:audioResource:newestAudio";
        //获取缓存数据
        Object cacheData = redisTemplate.opsForValue().get(cacheKey);
        if (cacheData instanceof List<?>) {
            List<?> list = (List<?>) cacheData;
            if (!CollectionUtils.isEmpty(list) && list.get(0) instanceof AudioResourceVo) {
                return (List<AudioResourceVo>) cacheData;
            }
            //缓存数据类型异常：删除脏数据
            redisTemplate.delete(cacheKey);
        }

        //查询数据库
        List<AudioResource> dbData = audioResourceMapper.getNewestAudio();
        List<AudioResourceVo> resultVo = convertToVo(dbData);
        //写缓存
        long ttl = CollectionUtils.isEmpty(resultVo) ? 1 : 6;
        redisTemplate.opsForValue().set(cacheKey, resultVo, ttl, TimeUnit.HOURS);
        return resultVo;
    }

    /**
     * 热新
     * @param pageNum
     * @param pageSize
     * @return
     */
    @Override
    public List<AudioResourceVo> getHotNewBooks(Integer pageNum, Integer pageSize) {
        // 1. 参数校验增强：处理null和非法值，限制最大pageSize
        if (pageNum == null || pageSize == null || pageNum <= 0 || pageSize <= 0) {
            log.warn("分页参数非法，pageNum: {}, pageSize: {}", pageNum, pageSize);
            return Collections.emptyList();
        }
        // 限制最大分页大小，防止过大查询拖慢数据库
        int maxPageSize = 10;
        if (pageSize > maxPageSize) {
            pageSize = maxPageSize;
        }

        // 2. 生成规范的缓存key
        String cacheKey = String.format("listenBook:audioResource:hotNewBooks:%d:%d", pageNum, pageSize);

        // 3. 尝试从缓存获取数据
        try {
            Object cacheData = redisTemplate.opsForValue().get(cacheKey);
            if (cacheData instanceof List<?>) {
                // 安全的类型转换：通过try-catch处理可能的类型异常
                try {
                    List<AudioResourceVo> cacheResult = (List<AudioResourceVo>) cacheData;
                    log.debug("最新作品缓存命中，cacheKey: {}", cacheKey);
                    return cacheResult;
                } catch (ClassCastException e) {
                    log.warn("缓存数据类型不匹配，删除脏数据，cacheKey: {}", cacheKey, e);
                    redisTemplate.delete(cacheKey);
                }
            }
        } catch (Exception e) {
            log.error("获取Redis缓存异常，将直接查询数据库，cacheKey: {}", cacheKey, e);
        }


        // 4. 查询数据库
        //计算分页
        int startIndex = (pageNum - 1) * pageSize;
        List<AudioResource> dbData = audioResourceMapper.getHotNewBooks(startIndex, pageSize);
        List<AudioResourceVo> resultVo = convertToVo(dbData);
        log.debug("最新作品数据库查询完成，数据量: {}", resultVo.size());

        // 5. 更新缓存（处理缓存设置异常）
        long ttl = CollectionUtils.isEmpty(resultVo) ? 1 : 7;
        try {
            redisTemplate.opsForValue().set(cacheKey, resultVo, ttl, TimeUnit.HOURS);
        } catch (Exception e) {
            redisTemplate.opsForValue().set(cacheKey, resultVo);
        }
        return resultVo;
    }

    @Override
    public List<AudioResourceVo> getClassicAudio() {
        String key = "listenBook:audioResource:classicAudio";
        List<AudioResourceVo> result;

        try {
            // 1. 从缓存获取数据
            Object cacheObj = redisTemplate.opsForValue().get(key);
            if (cacheObj != null) {
                // 安全校验类型，避免强转异常
                if (cacheObj instanceof List<?>) {
                    // 进一步校验集合元素类型
                    if (((List<?>) cacheObj).stream().allMatch(obj -> obj instanceof AudioResourceVo)) {
                        result = (List<AudioResourceVo>) cacheObj;
                        log.info("经典作品缓存命中，key:{}", key);
                        return result;
                    } else {
                        log.warn("缓存数据元素类型不匹配，key:{}", key);
                    }
                } else {
                    log.warn("缓存数据类型不是List，key:{}", key);
                    // 清除缓存
                    redisTemplate.delete(key);
                }
            }
        } catch (Exception e) {
            // 2. Redis操作异常时，不中断流程，继续查库
            log.error("获取经典作品缓存异常，key:{}, 异常信息:{}", key, e.getMessage(), e);
        }

        // 3. 缓存未命中或异常，查询数据库
        log.info("经典作品缓存未命中，查询数据库，key:{}", key);
        List<AudioResource> dbResult = audioResourceMapper.getClassicAudio();
        List<AudioResourceVo> voList = convertToVo(dbResult);

        // 4. 处理空结果（防止缓存穿透）
        if (voList == null) {
            voList = Collections.emptyList();
        }

        // 5. 将结果写入缓存
        try {
            // 经典作品更新频率低，可设置较长过期时间（如24小时），空结果设置较短时间（如5分钟）
            long expireTime = voList.isEmpty() ? 5 : 24 * 60;
            redisTemplate.opsForValue().set(key, voList, expireTime, TimeUnit.MINUTES);
            log.info("经典作品缓存设置成功，key:{}, 过期时间:{}分钟", key, expireTime);
        } catch (Exception e) {
            // 缓存写入失败不影响主流程，仅记录日志
            log.error("经典作品缓存设置异常，key:{}, 异常信息:{}", key, e.getMessage(), e);
        }

        return voList;
    }

    /**
     * 猜你喜欢
     * @param pageNum
     * @param pageSize
     * @return
     */
    @Override
    public List<AudioResourceVo> getGuessYouLike(Integer pageNum, Integer pageSize) {
        // 1. 校验分页参数
        if (pageNum == null || pageNum < 1) {
            pageNum = 1;
        }
        if (pageSize == null || pageSize < 1 || pageSize > 100) {
            pageSize = 20; // 限制最大页大小
        }
        //计算分页
        int startIndex = (pageNum - 1) * pageSize;
        //获取用户id
        LoginUser loginUser = LoginUserHolder.getLoginUser();
        //判断用户是否登入
        if(loginUser == null)
        {
            throw new BaseException(ResultCodeEnum.TOKEN_INVALID);
        }
        Long userId = loginUser.getUserId();
        //判断用户id是否大于0
        if(userId <= 0)
        {
            getRecommendRank(startIndex,pageSize);
        }
        //获取猜你喜欢
        List<AudioResource> guessYouLike = audioResourceMapper.getGuessYouLike(startIndex, pageSize,userId);
        if (guessYouLike == null)
        {
            return getRecommendRank(startIndex, pageSize);
        }
        return convertToVo(guessYouLike);
    }

    /**
     * 根据id获取音频
     * @param audioId
     * @return
     */
    @Override
    public AudioResourceVo getAudioById(Long audioId) {
        //根据音频ID获取音频
        AudioResource audioResource = audioResourceMapper.getAudioById(Math.toIntExact(audioId));
        //进行对象转换
        AudioResourceVo audioResourceVo = new AudioResourceVo();
        BeanUtils.copyProperties(audioResource,audioResourceVo);
        //设置音频的类型
        audioResourceVo.setAudioType(AudioType.getMessageByCode(audioResource.getAudioType()));
        return audioResourceVo;
    }

    /**
     * 获取相似音频
     * @param audioId
     * @return
     */
    @Override
    public List<AudioResourceVo> getSimilarAudio(Long audioId) {
        //根据音频ID获取音频
        AudioResource audioResource = audioResourceMapper.getAudioById(Math.toIntExact(audioId));
        //获取音频的分类ID
        Integer categoryId = audioResource.getCategoryId();
        //根据分类id + 音频id获取相似音频
        List<AudioResource> similarAudio = audioResourceMapper.getSimilarAudio(categoryId, audioId);
        //转换为VO
        return convertToVo(similarAudio);
    }

    /**
     * 索引音频
     */
    @Override
    public void indexAudios() {
        //查询所有音频
        List<AudioResource> audioResources = audioResourceMapper.getAllAudio();
        try {
            LuceneUtils.createAudioIndex(audioResources);
            log.info("索引音频成功");
        }catch (Exception e)
        {
            log.error("索引音频失败");
            throw new RuntimeException(e);
        }
    }

    /**
     * 搜索音频
     * @param keyword
     * @return
     */
    @Override
    public List<AudioResourceVo> searchAudio(String keyword) {
        try {
            List<AudioResourceVo> audioResourceVos = LuceneUtils.searchAudios(keyword);
            //添加搜索记录
            searchLogService.addSearchLog(keyword);
            return audioResourceVos;
        } catch (Exception e) {
            log.error("搜索音频失败");
            throw new RuntimeException(e);
        }
    }

    /**
     * 转换对象
     * @param audioResources
     * @return
     */
    private List<AudioResourceVo> convertToVo(List<AudioResource> audioResources) {
        //判断集合是否为空
        if (audioResources != null && !audioResources.isEmpty()) {
            return audioResources.stream().map(audioResource -> {
                AudioResourceVo audioResourceVo = new AudioResourceVo();
                BeanUtils.copyProperties(audioResource, audioResourceVo);
                audioResourceVo.setAudioType(AudioType.getMessageByCode(audioResource.getAudioType()));
                return audioResourceVo;
            }).collect(Collectors.toList());
        }
        return Collections.emptyList();
    }

    /**
     * 启动锁续期（看门狗）：每1秒续期3秒，直到任务被取消或异常
     * @param lockKey 锁Key
     * @param lockValue 锁Value（唯一标识持有者）
     * @return 续期任务的Future（用于后续取消）
     */
    private Future<?> startLockRenewal(String lockKey, String lockValue) {
        // 续期逻辑：仅当锁仍属于当前持有者时才续期
        Runnable renewalTask = () -> {
            try {
                String luaScript = "if redis.call('get', KEYS[1]) == ARGV[1] then " +
                        "return redis.call('expire', KEYS[1], ARGV[2]) " +
                        "else return 0 end";
                DefaultRedisScript<Long> redisScript = new DefaultRedisScript<>(luaScript, Long.class);
                // 执行Lua脚本：原子判断+续期（续期3秒，与初始锁过期时间一致）
                Long result = redisTemplate.execute(redisScript,
                        Collections.singletonList(lockKey),
                        lockValue, "3");
                if (result != null && result > 0) {
                    log.debug("推荐榜单锁续期成功，lockKey：{}", lockKey);
                } else {
                    log.warn("推荐榜单锁续期失败（锁已过期或被其他线程持有），lockKey：{}", lockKey);
                }
            } catch (Exception e) {
                log.error("推荐榜单锁续期任务异常，lockKey：{}", lockKey, e);
            }
        };
        // 延迟1秒后开始续期，每1秒执行一次（避免刚加锁就续期，浪费资源）
        return renewalExecutor.scheduleAtFixedRate(
                renewalTask,
                1,  // 初始延迟1秒
                1,  // 执行间隔1秒
                TimeUnit.SECONDS
        );
    }


    /**
     * 重试获取缓存：最多重试3次，每次间隔500ms
     * @param cacheKey 缓存Key
     */
    private List<AudioResourceVo> retryGet(String cacheKey) {
        // 重试次数
        int retryCount = 0;
        // 最大重试次数
        int RETRY_MAX_COUNT = 3;
        // 重试间隔（毫秒）
        long RETRY_INTERVAL_MS = 500;
        while (retryCount < RETRY_MAX_COUNT) {
            try {
                // 重试间隔：避免频繁请求
                Thread.sleep(RETRY_INTERVAL_MS);
                retryCount++;

                // 查缓存
                Object cacheData = redisTemplate.opsForValue().get(cacheKey);
                if (cacheData instanceof List<?> && !CollectionUtils.isEmpty((List<?>) cacheData)) {
                    return (List<AudioResourceVo>) cacheData;
                }

            } catch (InterruptedException e) {
                Thread.currentThread().interrupt(); // 恢复中断状态
                break;
            }
        }

        // 重试失败：返回空列表（避免返回null，上层无需处理null）
        return Collections.emptyList();
    }


    /**
     * 安全释放锁：使用Lua脚本原子判断+删除，避免误删其他线程的锁
     * @param lockKey 锁Key
     * @param lockValue 锁Value（当前持有者标识）
     */
    private void releaseLockSafely(String lockKey, String lockValue) {
        String luaScript = "if redis.call('get', KEYS[1]) == ARGV[1] then " +
                "return redis.call('del', KEYS[1]) " +
                "else return 0 end";
        DefaultRedisScript<Long> redisScript = new DefaultRedisScript<>(luaScript, Long.class);
        try {
            Long result = redisTemplate.execute(redisScript,
                    Collections.singletonList(lockKey),
                    lockValue);
            if (result == null || result == 0) {
                log.warn("释放推荐榜单锁失败（锁已过期或被其他线程持有），lockKey：{}", lockKey);
            }
        } catch (Exception e) {
            log.error("释放推荐榜单锁异常，lockKey：{}，lockValue：{}", lockKey, lockValue, e);
        }
    }




}
